﻿using System.Collections.Generic;
using System.Linq;

namespace Sidvall.Security
{
    public class ResourceItemCollection : System.Collections.ObjectModel.KeyedCollection<string, ResourceItem>
    {
        #region Public Members

        #region Add

        public void Add(ResourceItem item, ResourceItemAccessLevel accessLevel)
        {
            if (item == null)
                return;
            Add(item.Resource, item.Action, item.SubAction, item.ResourceItemId, item.Description, accessLevel);
        }
        public void Add(ResourceItem item, ResourceItemAccessLevel accessLevel, string description)
        {
            if (item == null)
                return;
            Add(item.Resource, item.Action, item.SubAction, item.ResourceItemId, description, accessLevel);
        }
        public void Add(string resource)
        {
            Add(resource, null);
        }
        public void Add(string resource, string action)
        {
            Add(resource, action, null);
        }
        public void Add(string resource, string action, string subAction)
        {
            Add(resource, action, subAction, null);
        }
        public void Add(string resource, string action, string subAction, string resourceItemId)
        {
            Add(resource, action, subAction, resourceItemId, null, ResourceItemAccessLevel.Unknown);
        }
        public void Add(string resource, string action, string subAction, string resourceItemId, string description, ResourceItemAccessLevel accessLevel)
        {
            ResourceItem item;

            if (string.IsNullOrWhiteSpace(resource))
            {
                item = new ResourceItem()
                {
                    Resource = null,
                    ResourceItemId = null,
                    Action = null,
                    SubAction = null,
                    Description = description,
                    AccessLevel = accessLevel,
                };
            }
            else if (string.IsNullOrWhiteSpace(action))
            {
                item = new ResourceItem()
                {
                    Resource = resource,
                    ResourceItemId = resourceItemId,
                    Action = null,
                    SubAction = null,
                    Description = description,
                    AccessLevel = accessLevel,
                };
            }
            else
            {
                item = new ResourceItem()
                {
                    Resource = resource,
                    ResourceItemId = resourceItemId,
                    Action = action,
                    SubAction = subAction,
                    Description = description,
                    AccessLevel = accessLevel,
                };
            }
            if (!Contains(item.GetKey()))
                Add(item);
        }

        #endregion
        #region AddWithDescription

        public void AddWithDescription(ResourceItem item, string description)
        {
            if (item == null)
                return;
            Add(item.Resource, item.Action, item.SubAction, item.ResourceItemId, description, item.AccessLevel);
        }

        #endregion
        #region AddWithSubAction

        public void AddWithSubAction(ResourceItem item, string subAction)
        {
            if (item == null)
                return;
            Add(item.Resource, item.Action, subAction, item.ResourceItemId, item.Description, item.AccessLevel);
        }
        public void AddWithSubAction(ResourceItem item, string subAction, string resourceItemId)
        {
            if (item == null)
                return;
            Add(item.Resource, item.Action, subAction, resourceItemId, item.Description, item.AccessLevel);
        }

        #endregion
        #region AddRange

        public void AddRange(IEnumerable<ResourceItem> items)
        {
            if (items == null)
                return;
            foreach (var item in items)
            {
                if ((item != null) && (!Contains(item.GetKey())))
                    Add(item);
            }
        }
        public void AddRange(IEnumerable<ResourceItem> items, ResourceItemAccessLevel accessLevel)
        {
            if (items == null)
                return;
            foreach (var item in items)
            {
                if ((item != null) && (!Contains(item.GetKey())))
                    Add(item, accessLevel);
            }
        }
        public void AddRange(IEnumerable<ResourceItem> items, ResourceItemAccessLevel accessLevel, string description)
        {
            if (items == null)
                return;
            foreach (var item in items)
            {
                if ((item != null) && (!Contains(item.GetKey())))
                    Add(item, accessLevel, description);
            }
        }
        public void AddRange(IEnumerable<string> resources, string action)
        {
            var items = CreateResourceItems(resources, action);
            AddRange(items);
        }
        public void AddRange(IEnumerable<string> resources, IEnumerable<string> actions)
        {
            var items = CreateResourceItems(resources, actions);
            AddRange(items);
        }
        public void AddRange(IEnumerable<string> resources, IEnumerable<string> actions, IEnumerable<string> subActions)
        {
            var items = CreateResourceItems(resources, actions, subActions);
            AddRange(items);
        }
        public void AddRange(IEnumerable<string> resources, IEnumerable<string> actions, IEnumerable<string> subActions, IEnumerable<string> resourceItemIds)
        {
            var items = CreateResourceItems(resources, actions, subActions, resourceItemIds);
            AddRange(items);
        }

        #endregion
        #region AddRangeWithDescription

        public void AddRangeWithDescription(IEnumerable<ResourceItem> items, string description)
        {
            if (items == null)
                return;
            foreach (var item in items)
            {
                if ((item != null) && (!Contains(item.GetKey())))
                    AddWithDescription(item, description);
            }
        }

        #endregion
        #region AddRangeWithSubAction

        public void AddRangeWithSubAction(IEnumerable<ResourceItem> items, string subAction)
        {
            if (items == null)
                return;
            foreach (var item in items)
            {
                if ((item != null) && (!Contains(item.GetKey())))
                    AddWithSubAction(item, subAction);
            }
        }
        public void AddRangeWithSubAction(IEnumerable<ResourceItem> items, string subAction, string resourceItemId)
        {
            if (items == null)
                return;
            foreach (var item in items)
            {
                if ((item != null) && (!Contains(item.GetKey())))
                    AddWithSubAction(item, subAction, resourceItemId);
            }
        }

        #endregion
        #region ContainsAny

        public bool ContainsAny(string resource)
        {
            return ContainsAny(resource, null, null, ResourceItemValidateLevel.Resource);
        }
        public bool ContainsAny(string resource, string action)
        {
            return ContainsAny(resource, action, null, ResourceItemValidateLevel.Action);
        }
        public bool ContainsAny(string resource, string action, string subAction)
        {
            return ContainsAny(resource, action, subAction, ResourceItemValidateLevel.SubAction);
        }
        private bool ContainsAny(string resource, string action, string subAction, ResourceItemValidateLevel validateLevel)
        {
            var items = FindItems(resource, action, subAction, validateLevel);
            return items.Any();
        }

        #endregion
        #region ContainsAnyWithExactMatch

        public bool ContainsAnyWithExactMatch(string resource)
        {
            return ContainsAnyWithExactMatch(resource, null, null, ResourceItemValidateLevel.Resource);
        }
        public bool ContainsAnyWithExactMatch(string resource, string action)
        {
            return ContainsAnyWithExactMatch(resource, action, null, ResourceItemValidateLevel.Action);
        }
        public bool ContainsAnyWithExactMatch(string resource, string action, string subAction)
        {
            return ContainsAnyWithExactMatch(resource, action, subAction, ResourceItemValidateLevel.SubAction);
        }
        private bool ContainsAnyWithExactMatch(string resource, string action, string subAction, ResourceItemValidateLevel validateLevel)
        {
            var item = FindItem(resource, action, subAction, validateLevel);
            if (item != null)
                return true;
            return false;
        }

        #endregion
        #region FindItem

        public ResourceItem FindItem(string resource)
        {
            return FindItem(resource, null, null, ResourceItemValidateLevel.Resource);
        }
        public ResourceItem FindItem(string resource, string action)
        {
            return FindItem(resource, action, null, ResourceItemValidateLevel.Action);
        }
        public ResourceItem FindItem(string resource, string action, string subAction)
        {
            return FindItem(resource, action, subAction, ResourceItemValidateLevel.SubAction);
        }
        private ResourceItem FindItem(string resource, string action, string subAction, ResourceItemValidateLevel validateLevel)
        {
            ResourceItem resourceItem;

            var item = new ResourceItem()
            {
                Resource = resource,
                Action = action,
                SubAction = subAction,
            };
            if (validateLevel == ResourceItemValidateLevel.SubAction)
            {
                resourceItem = (from o in this
                                where (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.Action.Equals(item.Action, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.SubAction.Equals(item.SubAction, System.StringComparison.OrdinalIgnoreCase))
                                select o).FirstOrDefault();
                if (resourceItem != null)
                    return resourceItem;
                validateLevel = ResourceItemValidateLevel.Action;
            }
            if (validateLevel == ResourceItemValidateLevel.Action)
            {
                resourceItem = (from o in this
                                where (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.Action.Equals(item.Action, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.SubAction.Length == 0)
                                select o).FirstOrDefault();
                if (resourceItem != null)
                    return resourceItem;
                validateLevel = ResourceItemValidateLevel.Resource;
            }
            if (validateLevel == ResourceItemValidateLevel.Resource)
            {
                resourceItem = (from o in this
                                where (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.Action.Length == 0)
                                    && (o.SubAction.Length == 0)
                                select o).FirstOrDefault();
                if (resourceItem != null)
                    return resourceItem;
            }
            return (from o in this
                    where (o.Resource.Length == 0)
                        && (o.Action.Length == 0)
                        && (o.SubAction.Length == 0)
                    select o).FirstOrDefault();
        }

        #endregion
        #region FindItemForResourceItemId

        public ResourceItem FindItemForResourceItemId(string resource, string resourceItemId)
        {
            return FindItemForResourceItemId(resource, resourceItemId, null, null, ResourceItemValidateLevel.Resource);
        }
        public ResourceItem FindItemForResourceItemId(string resource, string resourceItemId, string action)
        {
            return FindItemForResourceItemId(resource, resourceItemId, action, null, ResourceItemValidateLevel.Action);
        }
        public ResourceItem FindItemForResourceItemId(string resource, string resourceItemId, string action, string subAction)
        {
            return FindItemForResourceItemId(resource, resourceItemId, action, subAction, ResourceItemValidateLevel.SubAction);
        }
        private ResourceItem FindItemForResourceItemId(string resource, string resourceItemId, string action, string subAction, ResourceItemValidateLevel validateLevel)
        {
            ResourceItem resourceItem;

            var item = new ResourceItem()
            {
                Resource = resource,
                Action = action,
                SubAction = subAction,
                ResourceItemId = resourceItemId,
            };
            if (validateLevel == ResourceItemValidateLevel.SubAction)
            {
                resourceItem = (from o in this
                                where (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.ResourceItemId.Equals(item.ResourceItemId, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.Action.Equals(item.Action, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.SubAction.Equals(item.SubAction, System.StringComparison.OrdinalIgnoreCase))
                                select o).FirstOrDefault();
                if (resourceItem != null)
                    return resourceItem;
                validateLevel = ResourceItemValidateLevel.Action;
            }
            if (validateLevel == ResourceItemValidateLevel.Action)
            {
                resourceItem = (from o in this
                                where (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.ResourceItemId.Equals(item.ResourceItemId, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.Action.Equals(item.Action, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.SubAction.Length == 0)
                                select o).FirstOrDefault();
                if (resourceItem != null)
                    return resourceItem;
                validateLevel = ResourceItemValidateLevel.Resource;
            }
            if (validateLevel == ResourceItemValidateLevel.Resource)
            {
                resourceItem = (from o in this
                                where (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.ResourceItemId.Equals(item.ResourceItemId, System.StringComparison.OrdinalIgnoreCase))
                                    && (o.Action.Length == 0)
                                    && (o.SubAction.Length == 0)
                                select o).FirstOrDefault();
                if (resourceItem != null)
                    return resourceItem;
            }
            return (from o in this
                    where (o.Resource.Length == 0)
                        && (o.ResourceItemId.Length == 0)
                        && (o.Action.Length == 0)
                        && (o.SubAction.Length == 0)
                    select o).FirstOrDefault();
        }

        #endregion
        #region FindItems

        public IEnumerable<ResourceItem> FindItems(string resource)
        {
            return FindItems(resource, null, null, ResourceItemValidateLevel.Resource);
        }
        public IEnumerable<ResourceItem> FindItems(string resource, string action)
        {
            return FindItems(resource, action, null, ResourceItemValidateLevel.Action);
        }
        public IEnumerable<ResourceItem> FindItems(string resource, string action, string subAction)
        {
            return FindItems(resource, action, subAction, ResourceItemValidateLevel.SubAction);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private IEnumerable<ResourceItem> FindItems(string resource, string action, string subAction, ResourceItemValidateLevel validateLevel)
        {
            var item = new ResourceItem()
            {
                Resource = resource,
                Action = action,
                SubAction = subAction,
            };
            switch (validateLevel)
            {
                case ResourceItemValidateLevel.Resource:
                    return (from o in this
                            where ((o.Resource.Length == 0) || (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase)))
                            orderby o.Resource, o.ResourceItemId, o.Action, o.SubAction
                            select o);
                case ResourceItemValidateLevel.Action:
                    return (from o in this
                            where ((o.Resource.Length == 0) || (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.Action.Length == 0) || (o.Action.Equals(item.Action, System.StringComparison.OrdinalIgnoreCase)))
                            orderby o.Resource, o.ResourceItemId, o.Action, o.SubAction
                            select o);
                case ResourceItemValidateLevel.SubAction:
                    return (from o in this
                            where ((o.Resource.Length == 0) || (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.Action.Length == 0) || (o.Action.Equals(item.Action, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.SubAction.Length == 0) || (o.SubAction.Equals(item.SubAction, System.StringComparison.OrdinalIgnoreCase)))
                            orderby o.Resource, o.ResourceItemId, o.Action, o.SubAction
                            select o);
                default:
                    return this;
            }
        }

        #endregion
        #region FindItemsForResourceItemId

        public IEnumerable<ResourceItem> FindItemsForResourceItemId(string resource, string resourceItemId)
        {
            return FindItemsForResourceItemId(resource, resourceItemId, null, null, ResourceItemValidateLevel.Resource);
        }
        public IEnumerable<ResourceItem> FindItemsForResourceItemId(string resource, string resourceItemId, string action)
        {
            return FindItemsForResourceItemId(resource, resourceItemId, action, null, ResourceItemValidateLevel.Action);
        }
        public IEnumerable<ResourceItem> FindItemsForResourceItemId(string resource, string resourceItemId, string action, string subAction)
        {
            return FindItemsForResourceItemId(resource, resourceItemId, action, subAction, ResourceItemValidateLevel.SubAction);
        }
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
        private IEnumerable<ResourceItem> FindItemsForResourceItemId(string resource, string resourceItemId, string action, string subAction, ResourceItemValidateLevel validateLevel)
        {
            var item = new ResourceItem()
            {
                Resource = resource,
                Action = action,
                SubAction = subAction,
                ResourceItemId = resourceItemId,
            };
            switch (validateLevel)
            {
                case ResourceItemValidateLevel.Resource:
                    return (from o in this
                            where ((o.Resource.Length == 0) || (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.ResourceItemId.Length == 0) || (o.ResourceItemId.Equals(item.ResourceItemId, System.StringComparison.OrdinalIgnoreCase)))
                            orderby o.Resource, o.ResourceItemId, o.Action, o.SubAction
                            select o);
                case ResourceItemValidateLevel.Action:
                    return (from o in this
                            where ((o.Resource.Length == 0) || (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.ResourceItemId.Length == 0) || (o.ResourceItemId.Equals(item.ResourceItemId, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.Action.Length == 0) || (o.Action.Equals(item.Action, System.StringComparison.OrdinalIgnoreCase)))
                            orderby o.Resource, o.ResourceItemId, o.Action, o.SubAction
                            select o);
                case ResourceItemValidateLevel.SubAction:
                    return (from o in this
                            where ((o.Resource.Length == 0) || (o.Resource.Equals(item.Resource, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.ResourceItemId.Length == 0) || (o.ResourceItemId.Equals(item.ResourceItemId, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.Action.Length == 0) || (o.Action.Equals(item.Action, System.StringComparison.OrdinalIgnoreCase)))
                                && ((o.SubAction.Length == 0) || (o.SubAction.Equals(item.SubAction, System.StringComparison.OrdinalIgnoreCase)))
                            orderby o.Resource, o.ResourceItemId, o.Action, o.SubAction
                            select o);
                default:
                    return this;
            }
        }

        #endregion
        #region GetKeyForItem

        protected override string GetKeyForItem(ResourceItem item)
        {
            if (item == null)
                throw new System.ArgumentNullException("item");
            return item.ToString();
        }

        #endregion
        #region GetHighestAccessLevel

        public ResourceItemAccessLevel GetHighestAccessLevel()
        {
            return GetHighestAccessLevel(ResourceItemAccessLevel.Unknown);
        }
        public ResourceItemAccessLevel GetHighestAccessLevel(ResourceItemAccessLevel unknownLevel)
        {
            ResourceItemAccessLevel currentLevel;
            var itemLevel = ResourceItemAccessLevel.None;
            foreach (var item in this)
            {
                if (item.AccessLevel != ResourceItemAccessLevel.Unknown)
                    currentLevel = item.AccessLevel;
                else
                    currentLevel = unknownLevel;
                switch (currentLevel)
                {
                    case ResourceItemAccessLevel.Unknown:
                        return ResourceItemAccessLevel.Unknown;
                    case ResourceItemAccessLevel.Protected:
                        if (itemLevel == ResourceItemAccessLevel.None)
                            itemLevel = ResourceItemAccessLevel.Protected;
                        break;
                    case ResourceItemAccessLevel.Public:
                        itemLevel = ResourceItemAccessLevel.Public;
                        break;
                    default:
                        break;
                }
            }
            return itemLevel;
        }

        #endregion
        #region GetLowestAccessLevel

        public ResourceItemAccessLevel GetLowestAccessLevel()
        {
            return GetLowestAccessLevel(ResourceItemAccessLevel.Unknown);
        }
        public ResourceItemAccessLevel GetLowestAccessLevel(ResourceItemAccessLevel unknownLevel)
        {
            ResourceItemAccessLevel currentLevel;
            var itemLevel = ResourceItemAccessLevel.Public;
            foreach (var item in this)
            {
                if (item.AccessLevel != ResourceItemAccessLevel.Unknown)
                    currentLevel = item.AccessLevel;
                else
                    currentLevel = unknownLevel;
                switch (currentLevel)
                {
                    case ResourceItemAccessLevel.Unknown:
                        return ResourceItemAccessLevel.Unknown;
                    case ResourceItemAccessLevel.Protected:
                        if (itemLevel == ResourceItemAccessLevel.Public)
                            itemLevel = ResourceItemAccessLevel.Protected;
                        break;
                    case ResourceItemAccessLevel.None:
                        itemLevel = ResourceItemAccessLevel.None;
                        break;
                    default:
                        break;
                }
            }
            return itemLevel;
        }

        #endregion
        #region IsAuthorized

        public bool IsAuthorized(bool authorizeProtected)
        {
            return IsAuthorized(authorizeProtected, ResourceItemAccessLevel.Unknown);
        }
        public bool IsAuthorized(bool authorizeProtected, ResourceItemAccessLevel unknownLevel)
        {
            var accessLevel = GetLowestAccessLevel(unknownLevel);
            switch (accessLevel)
            {
                case ResourceItemAccessLevel.Public:
                    return true;
                case ResourceItemAccessLevel.Protected:
                    return authorizeProtected;
                default:
                    return false;
            }
        }

        #endregion
        #region SetAccessLevel

        public void SetAccessLevel(ResourceItemAccessLevel accessLevel)
        {
            foreach (var item in this)
                item.AccessLevel = accessLevel;
        }

        #endregion
        #region SetAccessLevelForAction

        public void SetAccessLevelForAction(ResourceItemAccessLevel accessLevel, string action)
        {
            if (action == null)
                action = string.Empty;
            var items = (from o in this
                         where o.Action.Equals(action, System.StringComparison.OrdinalIgnoreCase)
                         select o);
            foreach (var item in items)
                item.AccessLevel = accessLevel;
        }

        #endregion
        #region SetAccessLevelForResource

        public void SetAccessLevelForResource(ResourceItemAccessLevel accessLevel, string resource)
        {
            if (resource == null)
                resource = string.Empty;
            var items = (from o in this
                         where o.Resource.Equals(resource, System.StringComparison.OrdinalIgnoreCase)
                         select o);
            foreach (var item in items)
                item.AccessLevel = accessLevel;
        }

        #endregion
        #region ToString

        public override string ToString()
        {
            return ToString("; ");
        }
        public string ToString(string separator)
        {
            var sb = new System.Text.StringBuilder();
            foreach (var item in this)
            {
                if (sb.Length > 0)
                    sb.Append(separator);
                sb.Append(item.ToString());
            }
            return sb.ToString();
        }

        #endregion

        #region CreateResourceItems

        public static ResourceItemCollection CreateResourceItems(IEnumerable<string> resources, string action)
        {
            return CreateResourceItems(resources, new string[] { action });
        }
        public static ResourceItemCollection CreateResourceItems(IEnumerable<string> resources, IEnumerable<string> actions)
        {
            return CreateResourceItems(resources, actions, null);
        }
        public static ResourceItemCollection CreateResourceItems(IEnumerable<string> resources, IEnumerable<string> actions, IEnumerable<string> subActions)
        {
            return CreateResourceItems(resources, actions, subActions, null);
        }
        public static ResourceItemCollection CreateResourceItems(IEnumerable<string> resources, IEnumerable<string> actions, IEnumerable<string> subActions, IEnumerable<string> resourceItemIds)
        {
            return CreateResourceItems(resources, actions, subActions, resourceItemIds, ResourceItemAccessLevel.Unknown);
        }
        public static ResourceItemCollection CreateResourceItems(IEnumerable<string> resources, IEnumerable<string> actions, IEnumerable<string> subActions, IEnumerable<string> resourceItemIds, ResourceItemAccessLevel accessLevel)
        {
            var items = new ResourceItemCollection();
            if ((resources == null) || (!resources.Any()))
                resources = new string[] { string.Empty };
            if ((actions == null) || (!actions.Any()))
                actions = new string[] { string.Empty };
            if ((subActions == null) || (!subActions.Any()))
                subActions = new string[] { string.Empty };
            if ((resourceItemIds == null) || (!resourceItemIds.Any()))
                resourceItemIds = new string[] { string.Empty };
            foreach (var resource in resources)
            {
                foreach (var action in actions)
                {
                    foreach (var subAction in subActions)
                    {
                        foreach (var resourceItemId in resourceItemIds)
                            items.Add(resource, action, subAction, resourceItemId, null, accessLevel);
                    }
                }
            }
            return items;
        }

        #endregion

        #endregion
    }
}
